`include "common.svh"
`define FREELIST_DIFFTEST
`ifdef FREELIST_DIFFTEST
import "DPI-C" function int freelist_create(
  int id,
  int size,
  string name
);
import "DPI-C" function void freelist_free(
  int id,
  int value
);
import "DPI-C" function void freelist_allocate(
  int id,
  int value
);
import "DPI-C" function void freelist_redirect(
  int id,
  int rptr
);
`endif
module freelist #(
    parameter ALLOCATE_WIDTH = 2,
    parameter FREE_WIDTH = 4,
    localparam AW = `PRF_AW
) (
    input clk,
    input rst,

    input i_free_valid[FREE_WIDTH-1:0],
    output i_free_ready,
    input PRF_IDX i_free_entry[FREE_WIDTH-1:0],

    output o_allocate_valid[ALLOCATE_WIDTH-1:0],
    input o_allocate_ready[ALLOCATE_WIDTH-1:0],
    output PRF_IDX o_allocate_entry[ALLOCATE_WIDTH-1:0],
    output FREELIST_IDX o_allocate_rptr[ALLOCATE_WIDTH-1:0],

    input i_redirect_valid,
    input FREELIST_IDX i_redirect_rptr

);


  logic [FREE_WIDTH-1:0] i_free_valid_pack;
  logic [ALLOCATE_WIDTH-1:0] o_allocate_valid_pack, o_allocate_ready_pack, o_allocate_fire_pack;
  pack #(1, FREE_WIDTH) pack_free_valid (
      .din (i_free_valid),
      .dout(i_free_valid_pack)
  );
  pack #(1, ALLOCATE_WIDTH) pack_allocate_valid (
      .din (o_allocate_valid),
      .dout(o_allocate_valid_pack)
  );
  pack #(1, ALLOCATE_WIDTH) pack_allocate_ready (
      .din (o_allocate_ready),
      .dout(o_allocate_ready_pack)
  );

  wire i_free_fire = (|i_free_valid_pack) & i_free_ready;
  assign o_allocate_fire_pack = o_allocate_valid_pack & o_allocate_ready_pack;
  wire o_allocate_fire = |o_allocate_fire_pack;
  FREELIST_IDX wptr, rptr, wptr_next, rptr_next;
  logic [AW:0] o_allocate_num, i_free_num;
  integer i;
  always_comb begin
    o_allocate_num = 'b0;
    i_free_num = 'b0;
    for (i = 0; i < ALLOCATE_WIDTH; i = i + 1) begin
      o_allocate_num = o_allocate_num + {{AW{1'b0}}, o_allocate_valid[i] & o_allocate_ready[i]};
    end
    for (i = 0; i < FREE_WIDTH; i = i + 1) begin
      i_free_num = i_free_num + {{AW{1'b0}}, i_free_valid[i]};
    end
  end

  assign wptr_next = wptr + i_free_num;
  assign rptr_next = rptr + o_allocate_num;
  localparam RST_SIZE = `PRF_SIZE - `ARF_SIZE;
  reg_lr #((AW + 1), RST_SIZE[AW:0]) wptr_r (
      .clk,
      .rst (rst),
      .load(i_free_fire),
      .din (wptr_next),
      .dout(wptr)
  );

  reg_lr #((AW + 1)) rptr_r (
      .clk,
      .rst (rst),
      .load(o_allocate_fire | i_redirect_valid),
      .din (i_redirect_valid ? i_redirect_rptr : rptr_next),
      .dout(rptr)
  );


  PRF_IDX freelist_reg[(2**AW)-1:0];

  // wire [AW:0] entry_cnt = wptr - rptr;
  wire [AW:0] entry_cnt_next = (i_free_fire ? wptr_next : wptr) - (i_redirect_valid ? i_redirect_rptr : o_allocate_fire ? rptr_next : rptr);
  // assign i_free_ready = entry_cnt <= `PRF_SIZE - FREE_WIDTH;
  reg_r #(1) i_free_ready_r (
      .din (entry_cnt_next <= `PRF_SIZE - FREE_WIDTH),
      .dout(i_free_ready),
      .*
  );
  genvar gi;
  generate
    for (gi = 0; gi < ALLOCATE_WIDTH; gi = gi + 1) begin : Allocate
      wire [AW:0] raddr = rptr + gi;
      // reg_next#(AW+1) o_allocate_rptr_r (.din(raddr),.dout(o_allocate_rptr[gi]),.*);
      // reg_next#(AW) o_allocate_entry_r (.din(freelist_reg[raddr[AW-1:0]]),.dout(o_allocate_entry[gi]),.*);
      assign o_allocate_rptr[gi]  = raddr;
      assign o_allocate_entry[gi] = freelist_reg[raddr[AW-1:0]];
      reg_r #(1) o_allocate_valid_r (
          .din (gi < entry_cnt_next),
          .dout(o_allocate_valid[gi]),
          .*
      );
      // assign o_allocate_valid[gi] = gi < entry_cnt_next;
    end
    logic we[FREE_WIDTH-1:0];
    logic [AW-1:0] waddr[FREE_WIDTH-1:0];
    PRF_IDX wdata[FREE_WIDTH-1:0];

    for (gi = 0; gi < FREE_WIDTH; gi = gi + 1) begin : FreeListWrite
      assign wdata[gi] = i_free_entry[gi];
      assign waddr[gi] = wptr[AW-1:0] + gi;
      assign we[gi] = i_free_ready & i_free_valid[gi];
    end

    integer j;
    always_ff @(posedge clk) begin
      if (rst) begin
        for (j = `ARF_SIZE; j < `PRF_SIZE; j = j + 1) begin
          freelist_reg[j-`ARF_SIZE] <= j[AW-1:0];
        end
      end else begin
        for (j = 0; j < FREE_WIDTH; j = j + 1) begin
          if (we[j]) begin
            freelist_reg[waddr[j]] <= wdata[j];
          end
        end
      end
    end
  endgenerate


`ifdef FREELIST_DIFFTEST
  generate
    int id = 0;
    integer diff_i;
    int allocate_value[ALLOCATE_WIDTH-1:0], free_value[FREE_WIDTH-1:0], redirect_rptr;
    always_comb begin
      for (diff_i = 0; diff_i < ALLOCATE_WIDTH; diff_i = diff_i + 1) begin
        allocate_value[diff_i] = 'b0;
        allocate_value[diff_i][AW-1:0] = o_allocate_entry[diff_i];
      end
      for (diff_i = 0; diff_i < FREE_WIDTH; diff_i = diff_i + 1) begin
        free_value[diff_i] = 'b0;
        free_value[diff_i][AW-1:0] = i_free_entry[diff_i];
      end
      redirect_rptr = 'b0;
      redirect_rptr[AW-1:0] = i_redirect_rptr[AW-1:0];
    end
    integer diff_j;
    always_ff @(posedge clk) begin
      if (rst) begin
        id <= freelist_create(id, (2 ** AW), $sformatf("%m"));
        for (diff_j = 0; diff_j < `PRF_SIZE - `ARF_SIZE; diff_j = diff_j + 1) begin
          freelist_free(id, diff_j + `ARF_SIZE);
        end
      end else begin

        if (i_free_fire) begin
          for (diff_j = 0; diff_j < FREE_WIDTH; diff_j = diff_j + 1) begin
            if (i_free_valid_pack[diff_j]) freelist_free(id, free_value[diff_j]);
          end
        end
        if (i_redirect_valid) begin
          freelist_redirect(id, redirect_rptr);
          if(entry_cnt_next != (`PRF_SIZE-`ARF_SIZE)) begin
            $display("%m entry cnt error after redirect");
            $finish;
          end
        end else begin
          if (o_allocate_fire) begin
            for (diff_j = 0; diff_j < ALLOCATE_WIDTH; diff_j = diff_j + 1) begin
              if (o_allocate_fire_pack[diff_j]) freelist_allocate(id, allocate_value[diff_j]);
            end
          end
        end
      end
    end
  endgenerate
`endif
endmodule
